#include "StdAfx.h"
#include "ClassTree.h"
#include <wx/imaglist.h>
#include <wx/wupdlock.h>
#include "TagStorage.h"

#include "yasli/Macros.h"
#include "yasli/MemoryWriter.h"

#include <algorithm>
#include <iostream>
#include <malloc.h>
#ifdef WIN32
# include <windows.h>
#else
# include <string.h>
#endif
#include "Debug.h"

BEGIN_EVENT_TABLE(ClassTree, wxTreeCtrl)
	EVT_KEY_DOWN(ClassTree::onKeyDown)
	EVT_TREE_SEL_CHANGED(wxID_ANY, ClassTree::onSelectionChanged)
END_EVENT_TABLE()

struct TagItemData : wxTreeItemData{
    std::string title;
    TagType type;
	const Tag* declaration;
	const Tag* definition;

	TagItemData(const char* _title = "")
    : title(_title)
    , declaration(0)
    , definition(0)
	{        
	}

    int iconIndex() const
    {
        if(declaration && definition)
            return 10;
        else{
            if(declaration)
			{
				if(declaration->type == TAG_FUNCTION)
					return 9;
				else
					return int(declaration->type) + 1;
			}
            if(definition)
                return int(definition->type) + 1;
        }
        return 0;
    }
};

ClassTree::ClassTree(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size)
: imageList_(new wxImageList(16, 16))
, selectedTag_(0)
{
    #include "res/class_unavailable.xpm"
    #include "res/class.xpm"
    #include "res/enum.xpm"
    #include "res/method_impl.xpm"
    #include "res/member.xpm"
    #include "res/typedef.xpm"
    #include "res/define.xpm"
    #include "res/unknown.xpm"
    
    #include "res/method_decl.xpm"
    #include "res/method_decl_impl.xpm"
	imageList_->Add(wxBitmap((const char* const*)class_unavailable_xpm));     // 0
	imageList_->Add(wxBitmap((const char* const*)class_xpm));                 // 1
	imageList_->Add(wxBitmap((const char* const*)enum_xpm));                  // 2
	imageList_->Add(wxBitmap((const char* const*)method_impl_xpm));           // 3
	imageList_->Add(wxBitmap((const char* const*)typedef_xpm));               // 4
	imageList_->Add(wxBitmap((const char* const*)member_xpm));                // 5
	imageList_->Add(wxBitmap((const char* const*)enum_xpm)); // enum_value    // 6
	imageList_->Add(wxBitmap((const char* const*)define_xpm));                // 7
	imageList_->Add(wxBitmap((const char* const*)unknown_xpm));               // 8
	imageList_->Add(wxBitmap((const char* const*)method_decl_xpm));           // 9
	imageList_->Add(wxBitmap((const char* const*)method_decl_impl_xpm));      // 10
	Create(parent, id, pos, size);
	AssignImageList(imageList_);

    rootInfo_.items.push_back( GetRootItem() );
    rootInfo_.hasChildren = true;
}


ClassTree::~ClassTree()
{
	wxWindowUpdateLocker locker(this);
	DeleteAllItems();
}


bool ClassTree::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size)
{
	if(!wxTreeCtrl::Create(parent, id, pos, size, wxTR_HAS_BUTTONS | wxTR_HIDE_ROOT | wxTR_LINES_AT_ROOT ))
		return false;

	wxTreeItemId root;
    root = AddRoot(wxT("Root"));

	return true;
}

template<bool methodsFirst, bool groupByAccess>
struct MembersCompare
{
	bool operator()(const Tag* lhs, const Tag* rhs) const{
		if(methodsFirst){
			if(lhs->type == TAG_FUNCTION){
				if(rhs->type == TAG_FUNCTION){
					return compareSymbolName(lhs->name, rhs->name) < 0;
				}
				else
					return true;
			}
			else{
				if(rhs->type == TAG_FUNCTION)
					return false;
				else
					return compareSymbolName(lhs->name, rhs->name) < 0;
			}
		}
	}
};

struct Comparer{
	int operator()(const char* leftTitle, const char* rightTitle, TagType leftType, TagType rightType){

        int result = compareSymbolName(leftTitle, rightTitle);
        return result;
        /*
        if(result == 0)
            return (leftType < rightType) ? -1 : ((leftType == rightType) ? 0 : 1);
        else
            return result;
            */
	}
};


wxTreeItemId ClassTree::insertTagItemSorted(wxTreeItemId parent, const char* title, TagType type)
{
	wxTreeItemId insertedItem;
	Comparer comparer;

	wxTreeItemIdValue cookie;
	wxTreeItemId item = GetFirstChild(parent, cookie);
	int index = 0;
	while(item.IsOk()){
		const TagItemData* data = static_cast<TagItemData*>(GetItemData(item));
		ASSERT(data);

		// TODO tag can be NULL
        int compareResult = comparer(title, data->title.c_str(), type, data->type);
		if(compareResult < 0){
			insertedItem = InsertItem(parent, size_t(index), wxString(title, wxConvISO8859_1), 0, -1, new TagItemData());
			ASSERT(insertedItem.IsOk());
			break;
		}
        else if(compareResult == 0){
			insertedItem = item;
			// SetItemData(insertedItem, new TagItemData());
            break;
        }
		item = GetNextChild(parent, cookie);
		++index;
	}
	if(!insertedItem.IsOk())
		insertedItem = AppendItem(parent, wxString(title, wxConvISO8859_1), 0, -1, new TagItemData());

	return insertedItem;
}

int ClassTree::addTag(const Tag* tag)
{
	bool tagMatchFilter = matchFilter(*tag, false);
	bool tagMatchFilterRoot = matchFilter(*tag, true);
    if(!tagMatchFilter && !tagMatchFilterRoot)
        return 0;

	int count = 0;

    ClassInfo* parentInfo = findOrAddParentInfo(tag); 
    ASSERT(parentInfo);
    Items::iterator it;
    Items parentItems = parentInfo->items;
    parentItems.push_back(GetRootItem());
    FOR_EACH(parentItems, it){
        wxTreeItemId parent = *it;
        wxTreeItemId item;

        if(canBeParent(tag->type)){
			if((parent == GetRootItem() && tagMatchFilterRoot) || (parent != GetRootItem() && tagMatchFilter))
			{
				ClassInfo& info = classNameToInfo_[tag->nameFull];
				item = info.ghostItem;

				if(item.IsOk())
					info.ghostItem = wxTreeItemId();
				else{
					if(parent == GetRootItem())
						item = insertTagItemSorted(parent, tag->titleFull, tag->type); // TODO: titleFull for root
					else
						item = insertTagItemSorted(parent, tag->title, tag->type); // TODO: titleFull for root
					info.items.push_back(item); // addParentInfo(tag, item);
				}

				ASSERT(item.IsOk());
				addTagToItem(item, tag);
			}
        }
        else{
            if(parent == GetRootItem()){
				if(tagMatchFilterRoot){
					item = insertTagItemSorted(parent, tag->titleFull, tag->type); // TODO: titleFull for root
					ASSERT(item.IsOk());
					addTagToItem(item, tag);
				}
				else
					item = wxTreeItemId();
			}
			else{
				if(tagMatchFilter){
					item = insertTagItemSorted(parent, tag->title, tag->type); // TODO: titleFull for root
					ASSERT(item.IsOk());
					addTagToItem(item, tag);
				}
				else
					item = wxTreeItemId();
			}
        }

#ifndef NDEBUG
        if(item.IsOk() && parent != GetRootItem()){
            const Tag* parentTag = tagByItem(parent);
            Items& items = itemsByTag_[tag];
            ASSERT(std::find(items.begin(), items.end(), item) != items.end());
        }
#endif
        parentInfo->hasChildren = true;
		if(item.IsOk())
			++count;
    }

	int countedItems = countTagItems(tag);
	ASSERT(countedItems == count);
	return count;
}




void ClassTree::update()
{
	wxWindowUpdateLocker locker(this);
	if(tagStorage.cleared()){
		rebuild();
		return;
	}

	const TagStorage::DeadTags& deadTags = tagStorage.deadTags();
	const TagStorage::NewTags& newTags = tagStorage.newTags();

	MemoryWriter msg;
	msg << "Updating tree with " << int(deadTags.size()) << " dead tags, " << int(newTags.size()) << " new tags\n";
	TRACE(msg.c_str());

	TagStorage::DeadTags::const_iterator it;
	FOR_EACH(deadTags, it){
		const Tag* deadTag = *it;
		removeTag(deadTag);
	}
	removeEmptyGhosts();
	FOR_EACH(newTags, it){
		const Tag* newTag = *it;
		addTag(newTag);
	}
}

void ClassTree::removeEmptyGhosts()
{
	ClassNameToInfoMap::iterator it;
	for(it = classNameToInfo_.begin(); it != classNameToInfo_.end(); ++it)
	{
		Items& items = it->second.items;
		Items::iterator iit;
		for(iit = items.begin(); iit != items.end(); ){
			wxTreeItemId item = *iit;
            TagItemData* data = static_cast<TagItemData*>(GetItemData(item));
            if(data->declaration || data->definition || ItemHasChildren(item))
                ++iit;
            else{
                Delete(item);
                iit = items.erase(iit);
            }
		}
	}
}

void ClassTree::rebuild()
{
	wxWindowUpdateLocker locker(this);
	DeleteChildren(GetRootItem());
	itemsByTag_.clear();
	classNameToInfo_.clear();
	ghostItems_.clear();

	TagStorage::SortedTags::const_iterator it;
	for(int typeIndex = 0; typeIndex < TAG_TYPE_COUNT; ++typeIndex){
		TagType type = (TagType)typeIndex;
		const TagStorage::SortedTags& tags = tagStorage.sortedTags(TagType(typeIndex));

		FOR_EACH(tags, it){
			const Tag* tag = *it;
			addTag(tag);
		}
	}

	if(!filterMember_.empty()){
		wxTreeItemId root = GetRootItem();
		wxTreeItemIdValue cookie;
		wxTreeItemId current = GetFirstChild(root, cookie);
		while(current.IsOk()){
			if(!IsExpanded(current))
				Expand(current);
			current = GetNextChild(root, cookie);
		}
	}

	wxTreeItemId item = GetSelection();
	if(!item.IsOk() || item == GetRootItem()){
		wxTreeItemIdValue cookie;
		wxTreeItemId firstItem = GetFirstChild(GetRootItem(), cookie);
		if(firstItem.IsOk())
			ToggleItemSelection(firstItem);
	}

	verifyTree(GetRootItem());
}

bool nameMatchFilter(const char* tagName, const std::string& filter_)
{
	int len = strlen(tagName) + 1;
	char* name = (char*)alloca(len);
	memcpy(name, tagName, len);
#ifdef WIN32
	CharLower(name);
#else
	char* p = name;
	while(*p){
		*p = tolower(*p);
		++p;
	}
#endif
	if(filter_.empty())
		return true;
	else{
		if(filter_.c_str()[0] == ' ')
			return strstr(name, filter_.c_str() + 1) != 0;
		else
			return strstr(name, filter_.c_str()) == name;
	}
}


bool ClassTree::matchFilter(const char* singleName)
{
	return nameMatchFilter(singleName, filter_.c_str());
}

bool ClassTree::matchFilter(const Tag& tag, bool forRoot)
{
    if(tag.parentClass[0] == '\0'){
		if(!canBeParent(tag.type)){
			if(!filterMember_.empty())
				return false;
		}

        if(nameMatchFilter(tag.nameFull, filter_))
            return true;
        return nameMatchFilter(tag.name, filter_);
    }
    else{
		if(forRoot && (!filterMember_.empty() || filter_.empty()))
			return false;
		if(forRoot)
		    return nameMatchFilter(tag.nameFull, filter_);
		else
		{
			if(!nameMatchFilter(tag.parentClass, filter_))
				return false;
			if(nameMatchFilter(tag.nameFull, filterMember_))
				return true;
			return nameMatchFilter(tag.name, filterMember_);
		}
    }
}

const Tag* ClassTree::tagByItem(wxTreeItemId item, bool wantDeclaration)
{
	ASSERT(item.IsOk());
	ASSERT(item != GetRootItem());
	TagItemData* data = static_cast<TagItemData*>(GetItemData(item));
	ASSERT(data);
	if(!wantDeclaration && data->definition)
		return data->definition;
	return data->declaration;
}

int ClassTree::countTagItems(const Tag* tag)
{
	return itemsByTag_[tag].size();
}




void ClassTree::addParentInfo(const Tag* tag, wxTreeItemId item)
{
    ClassInfo& info = classNameToInfo_[tag->nameFull];
    if(info.ghostItem){
    }
    info.items.push_back(item);
}

ClassTree::ClassInfo* ClassTree::insertGhostClassItem(const char* nameFull)
{
	// always sorted
    ClassInfo& info = classNameToInfo_[nameFull];
    if(info.ghostItem.IsOk())
        return &info;
    else{
		if(matchFilter(nameFull)){
			info.ghostItem = insertTagItemSorted(GetRootItem(), nameFull, TAG_CLASS);
			info.items.push_back(info.ghostItem);
		}
        return &info;
    }
}

ClassTree::ClassInfo* ClassTree::findOrAddParentInfo(const Tag* tag)
{
    if(tag->parentClass[0] == '\0')
       return &rootInfo_;
    ClassNameToInfoMap::iterator it = classNameToInfo_.find(tag->parentClass);
    ClassInfo* info = 0;

    if(it != classNameToInfo_.end())
        return &it->second;
	else
		return insertGhostClassItem( tag->parentClass );

}

bool ClassTree::canBeParent(TagType type) const
{
	return type == TAG_CLASS || type == TAG_ENUM;
}


ClassTree::Items ClassTree::findItemsWithTag(const Tag* tag)
{
	return itemsByTag_[tag];
}

void ClassTree::addTagToItem(wxTreeItemId item, const Tag* tag)
{
	ASSERT(item.IsOk());

	TagItemData* data = static_cast<TagItemData*>(GetItemData(item));
	ASSERT(data);

    if(tag->prototype){
        //ASSERT(data->declaration == 0);
		if(data->declaration != 0){
			char msg[128];
			snprintf(msg, sizeof(msg), " replacing declaration, tag: %s\n", data->declaration->nameFull);
			TRACE(msg);
		}
        data->declaration = tag;
    } 
    else
    {
        //ASSERT(data->definition == 0);
		if(data->definition != 0){
			char msg[128];
			snprintf(msg, sizeof(msg), " replacing definition, tag: %s\n", data->definition->nameFull);
			TRACE(msg);
		}
        data->definition = tag;
    }
	if(data->title.empty())
		data->title = tag->title;
    data->type = tag->type;

	Items& items = itemsByTag_[tag];
	items.push_back(item);
    SetItemImage(item, data->iconIndex());
}

bool ClassTree::removeTagFromItem(wxTreeItemId item, const Tag* tag)
{
	ASSERT(item.IsOk());

	TagItemData* data = static_cast<TagItemData*>(GetItemData(item));
	ASSERT(data);
	ASSERT(data->declaration == tag || data->definition == tag);
	if(data->declaration == tag)
		data->declaration = 0;
	if(data->definition == tag)
		data->definition = 0;

	Items& items = itemsByTag_[tag];
	Items::iterator it = std::find(items.begin(), items.end(), item);
	ASSERT(it != items.end());
	items.erase(it);

    return !canBeParent(data->type) && data->declaration == 0 && data->definition == 0;
}

void ClassTree::removeTag(const Tag* tag)
{
	Items items = itemsByTag_[tag];
	Items::iterator iit;
	char msg[64];
	snprintf(msg, sizeof(msg), " total tags for next item: %i\n", items.size());
	TRACE(msg);;

	FOR_EACH(items, iit){
		wxTreeItemId item = *iit;
		ASSERT(item.IsOk());
		if(removeTagFromItem(item, tag)){
			Delete(item, true);
		}
	}
	ASSERT(itemsByTag_[tag].empty());
	itemsByTag_.erase(tag);
}

void ClassTree::Delete(wxTreeItemId item, bool dead)
{
	ASSERT(item.IsOk());
	if(item == GetSelection()){
		UnselectAll();
	}
	ASSERT(!ItemHasChildren(item));
	wxTreeCtrl::Delete(item);
}

bool ClassTree::refilter(wxTreeItemId item)
{
	bool isRoot = item == GetRootItem();
	if(!isRoot){
		if(filterMember_.empty() || filterMember_ == " ")
			return true;
		if(!IsExpanded(item))
			Expand(item);
	}
	else
		rebuild();

	return true;

	bool hasRequestedMembers = false;
	typedef std::vector<wxTreeItemId> Children;

	/*
	Children items;
	items.reserve(GetChildrenCount(item));
	wxTreeItemIdValue cookie;
	wxTreeItemId current = GetFirstChild(item, cookie);
	while(current.IsOk()){
		items.push_back(current);
		current = GetNextChild(item, cookie);
	}
	std::cout << "\nRefiltering...\n" << std::endl;

	Children::iterator it;
	FOR_EACH(items, it){
		wxTreeItemId current = *it;
#ifndef NDEBUG
		//verifyItem(current);
#endif
		const Tag* tag = tagByItem(current);
		TagItemData* data = static_cast<TagItemData*>(GetItemData(current));
		bool match = isRoot ? (tag ? matchFilter(*tag) : matchFilter(data->titleFull.c_str())) : ((tag ? matchFilterMember(*tag) : true));
		if(!match || ((!tag || tag->type == TAG_CLASS || tag->type == TAG_ENUM) && !refilter(current))){
			if(tag){
				std::cout << "delete " << tag->name << std::endl;
				removeTag(tag);
			}
			else{
				if(!ItemHasChildren(current))
					Delete(current);
			}
		}
		else
			hasRequestedMembers = true;
	}
	*/

	verifyTree(item);
	return hasRequestedMembers;
}


void ClassTree::verifyItem(wxTreeItemId item)
{
	/*
	const Tag* tag = tagByItem(item);
	ASSERT(tag);
	Items& items = itemsByTag_[tag];
	ASSERT(std::find(items.begin(), items.end(), item) != items.end());
	*/
}

void ClassTree::verifyTree(wxTreeItemId root)
{
	wxTreeItemIdValue cookie;
	wxTreeItemId current = GetFirstChild(root, cookie);
	while(current.IsOk()){
		verifyItem(current);
		verifyTree(current);
		current = GetNextChild(root, cookie);
	}
}

void ClassTree::setFilter(const char* filter, const char* memberFilter)
{
	wxWindowUpdateLocker updateLocker(this);

	bool needRebuild = false;

	std::vector<char> newFilter(filter, filter + strlen(filter) + 1);
	if(newFilter.size() > 1){
		char* p = &newFilter.front();
		while(p <= &newFilter.back()){
			*p = tolower(*p);
			++p;
		}
	}
	if(filter_.empty() || filter_ == " ")
		needRebuild = true;
	else if(strstr(&newFilter.front(), filter_.c_str()) != &newFilter.front())
		needRebuild = true;


	filter_ = &newFilter.front();

	std::vector<char> newMemberFilter(memberFilter, memberFilter + strlen(memberFilter) + 1);
	if(newMemberFilter.size() > 1){
		char* p = &newFilter.front();
		while(p <= &newFilter.back()){
			*p = tolower(*p);
			++p;
		}
	}

	if(!needRebuild && strcmp(&newMemberFilter.front(), filterMember_.c_str()) != 0){
		if(filterMember_.empty() || filterMember_ == " ")
			needRebuild = true;
		if(strstr(&newMemberFilter.front(), filterMember_.c_str()) != &newMemberFilter.front())
			needRebuild = true;
	}
	filterMember_ = &newMemberFilter.front();

	if(needRebuild)
		rebuild();
	else
		refilter(GetRootItem());
}

void ClassTree::onSelectionChanged(wxTreeEvent& event)
{
	wxTreeItemId item = event.GetItem();
	if(item == GetRootItem())
		return;
	if(item.IsOk()){
		if(GetItemData(event.GetItem()) == 0){
			std::cout << "WARNING: invalid item in onSelectionChanged" << std::endl;
			selectedTag_ = 0;
			return;
		}
		const Tag* tag = tagByItem(event.GetItem());
		selectedTag_ = tag;
	}
	else
		selectedTag_ = 0;
}

void ClassTree::serialize(Archive& ar)
{

}

void ClassTree::onKeyDown(wxKeyEvent& event)
{
	if(event.GetKeyCode() != WXK_UP &&
		event.GetKeyCode() != WXK_HOME &&
		event.GetKeyCode() != WXK_PAGEUP &&
		event.GetKeyCode() != WXK_TAB){
		event.Skip();
		return;
	}


	wxTreeItemId item = GetSelection();

	wxTreeItemIdValue cookie;
	wxTreeItemId firstItem = GetFirstChild(GetRootItem(), cookie);

	if(event.GetKeyCode() == WXK_TAB ||
		event.GetModifiers() & wxMOD_CONTROL || 
		!firstItem.IsOk() || (item.IsOk() && firstItem == item)){
		wxKeyEvent newEvent = event;
		wxWindow* parent = GetParent();
		newEvent.SetEventObject(parent);
		newEvent.SetId(parent->GetId());
		parent->GetEventHandler()->ProcessEvent(newEvent);
	}
	else
		event.Skip();
}
